package com.pinfly.common.http;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;

import com.pinfly.common.config.ConfigException;
import com.pinfly.common.config.PfyProperties;
import com.pinfly.common.config.PfyPropertiesFactory;
import com.pinfly.common.util.StringUtil;

/**
 * Helper class that manages a thread pool to service requests for HttpServer.
 * <p>
 * This class provides several static methods used by HttpServer to service
 * requests. It maintains a queue of outstanding requests, as well as a pool of
 * threads to service those requests. Threads are re-used as necessary, but a
 * request will never have to wait for a thread to become available. If there is
 * no available thread to service a request, a new thread is created.
 * <p>
 * Note these are all static methods, so the request queue and thread pool are
 * shared by all instances of HttpServer.
 * <p>
 * Methods are also provided for registering and unregistering servers. When the
 * last server is unregistered, all threads in the thread pool are stopped. This
 * allows the process to exit. If a new server is subsequently started, then a
 * new thread pool will be created.
 */
public class RequestHandler
{
    private static final Logger logger = Logger.getLogger (RequestHandler.class);

    // Default values for the configurable items.
    // These are used if the configurable items aren't specified or are invalid
    private static final int DEFAULT_IDLE_TIMEOUT = 5000; // Wait 5 seconds for
                                                          // another request
    private static final int DEFAULT_IDLE_POOL_SIZE = 20; // Number of idle
                                                          // processor threads
                                                          // to keep

    // The configuration values used at run time
    private static final int IDLE_TIMEOUT;
    private static final int IDLE_POOL_SIZE;

    /**
     * The queue of outstanding requests. When a new request is received, an
     * entry is made in this queue. When a thread is assigned to process the
     * request, the entry is removed from this queue.
     * <p>
     * This is also the object that is used for synchronization on all the
     * queues and lists maintained by this class.
     */
    private static List s_requestQ = new ArrayList ();
    /**
     * The set of all current requests. When a new request is received, it is
     * added to this set. When the request is completely finished, it is removed
     * from this set. This set is used to stop requests when a server is
     * stopped.
     */
    private static Set s_allRequests = new HashSet ();
    /**
     * The number of idle threads. This is incremented each time a thread is
     * created and each time a thread finishes processing a request. It is
     * decremented when a thread begins processing a request. Access to this
     * variable is synchronized on the s_requestQ object like the other queues.
     */
    private static int s_idleThreadCount = 0;
    /**
     * The set of all threads. Threads are added to this set when created and
     * removed when they are killed - i.e. when the last server is shutdown.
     */
    private static Set s_allThreads = new HashSet ();
    /**
     * The set of all servers. Servers are added to this set when started and
     * removed when stopped. When the size of this set reaches zero, all threads
     * are stopped.
     */
    private static HashSet s_servers = new HashSet ();

    // Get the values for the configurable parameters
    static
    {
        PfyProperties props = null;
        try
        {
            props = PfyPropertiesFactory.getProperties ("global");
        }
        catch (ConfigException e)
        {
            logger.warn (e.getMessage ());
        }

        IDLE_TIMEOUT = getPropValue (props, "http.server.idleTimeout", 5000, DEFAULT_IDLE_TIMEOUT);
        IDLE_POOL_SIZE = getPropValue (props, "http.server.idlePoolSize", 10, DEFAULT_IDLE_POOL_SIZE);
        if (logger.isTraceEnabled ())
        {
            StringBuffer buff = new StringBuffer ("RequestHandler configuration values\n");
            buff.append ("idleTimeout: ").append (IDLE_TIMEOUT).append ('\n').append ("idlePoolSize: ")
                .append (IDLE_POOL_SIZE);
            logger.trace (buff.toString ());
        }
    }

    private static int getPropValue (PfyProperties props, String key, int minValue, int defaultValue)
    {
        int value = defaultValue;
        String s = props.getProperty (key);
        try
        {
            value = Integer.parseInt (s);
            if (value < minValue)
            {
                logger.warn ("Value for " + key + " must be >= " + minValue + "; configured value of " + value
                             + " ignored.");
                value = defaultValue;
            }
        }
        catch (NumberFormatException fne)
        {
            // No message if it is missing, but if its there and invalid
            // tell someone about it.
            if (s != null)
            {
                logger.warn ("Value for " + key + " must be numeric; configured value of " + s + "ignored.");
            }
        }
        return value;
    }

    /**
     * Start a server
     * 
     * @param serviceName the name of the server
     * @param server the <code>HttpServer</code> instance for the server
     * @see stopServer
     */
    static void startServer (String serviceName, HttpServer server)
    {
        // Add this server to the set of servers
        synchronized (s_requestQ)
        {
            s_servers.add (server);
        }
    }

    /**
     * Stop a server. Any pending requests for the server are removed from the
     * queue. Any currently processing requests for the server are stopped.
     * <p>
     * If there are no more running servers, then the thread pool is destroyed.
     * 
     * @param serviceName the name of the server. This must match the name used
     *            to start the server in the call to {@link #startServer
     *            startServer} and the name used to create requests in
     *            {@link #serviceRequest serviceRequest}.
     * @param server the <code>HttpServer</code> instance for the server. This
     *            must be the same instance that was used when the server was
     *            started with {@link #startServer startServer}
     */
    static void stopServer (String serviceName, HttpServer server)
    {
        synchronized (s_requestQ)
        {
            // Remove this server from the set
            s_servers.remove (server);

            // If there are any requests associated with this server, get
            // rid of them
            for (int i = s_requestQ.size () - 1; i >= 0; i--)
            {
                Request req = (Request) s_requestQ.get (i);
                if (req.m_serviceName.equals (serviceName))
                {
                    s_requestQ.remove (i);
                }
            }

            for (Iterator it = s_allRequests.iterator (); it.hasNext ();)
            {
                Request req = (Request) it.next ();
                if (req.m_serviceName.equals (serviceName))
                {
                    req.stop ();
                    it.remove ();
                }
            }

            // If this is the last server to stop, get rid of all the threads
            if (s_servers.size () == 0)
            {
                for (Iterator it = s_allThreads.iterator (); it.hasNext ();)
                {
                    ServiceThread thread = (ServiceThread) it.next ();
                    thread.m_continue = false;
                    thread.interrupt ();
                }
                s_allThreads.clear ();
            }
        }
    }

    /**
     * Add a new request. The new request is added to the request queue. If
     * there are no idle threads available to service the request, a new thread
     * is created.
     * 
     * @param reqProc the {@link RequestProcessor} that will process requests
     *            received on the socket.
     * @param socket the <code>Socket</code> on which requests will be received.
     * @param serviceName the name of the server making the request.
     * @param clntInfo information about the client connection
     * @see startServer
     * @see stopServer
     */
    static void serviceRequest (RequestProcessor reqProc, Socket socket, String serviceName, ClientConnection clntInfo)
    {
        // Create a new request and add it to the request queue
        Request req = new Request (reqProc, socket, serviceName, clntInfo);
        synchronized (s_requestQ)
        {
            s_requestQ.add (req);
            s_allRequests.add (req);

            // If there aren't enough idle threads to service all outstanding
            // requests, create a new thread
            if (s_requestQ.size () > s_idleThreadCount)
            {
                ServiceThread thread = new ServiceThread ();
                // We need to add the thread to the idle list. The thread
                // itself can't do it when it starts, because we don't know
                // when it will actually get to run.
                s_idleThreadCount++;
                s_allThreads.add (thread);
                thread.start ();
            }
            s_requestQ.notifyAll ();
        }
    }

    /** The class is never instantiated */
    private RequestHandler ()
    {
    }

    /**
     * A request to be processed. When a thread is available to process the
     * request, this class' process method is called.
     */
    private static class Request
    {
        /** The socket on which requests are received from the client */
        private final Socket m_clientSocket;
        /** The request processor that handles requests */
        private final RequestProcessor m_rp;
        /** The name of the server making the request */
        private final String m_serviceName;
        /** Info about the client connection */
        private final ClientConnection m_clntInfo;

        /** Continue processing as long as this is true */
        private boolean m_continue = true;

        // Constants used for parsing HTTP requests and creating responses
        static final String CONTENTTYPE = "application/x-www-form-urlencoded";
        static final String HTTPHEADER1_0 = "HTTP/1.0";
        static final String HTTPHEADER1_1 = "HTTP/1.1";
        static final String CACHECONTROL1_0 = "Expires: Mon, 01 Jan 1990 00:00:00 GMT";
        static final String CACHECONTROL1_1 = "Cache-Control: max-age=0, must-revalidate";
        static final String CONNECTIONHEADER = "Connection:";
        static final String EXPECT = "Expect:";
        static final String EXPECT_CONTINUE = "100-continue";
        static final String CLOSECONNECTION = "close";
        static final String KEEPALIVE = "keep-alive";
        static final String HOST = "Host:";
        static final String CONTENTLENGTH = "Content-Length:";

        /**
         * Create a new request
         * 
         * @param reqProc the {@link RequestProcessor} that will process
         *            requests received on the socket.
         * @param socket the <code>Socket</code> on which requests will be
         *            received.
         * @param serviceName the name of the server making the request.
         * @param clntInfo information about the client connection
         */
        private Request (RequestProcessor reqProc, Socket socket, String serviceName, ClientConnection clntInfo)
        {
            m_rp = reqProc;
            m_clientSocket = socket;
            m_serviceName = serviceName;
            m_clntInfo = clntInfo;
        }

        /**
         * Process requests from the client.
         * <p>
         * When a thread is available to process this request, it calls this
         * method. This method reads requests from the client socket, parses
         * them, and calls the <code>RequestProcessor</code> to actually carry
         * out the requests.
         * <p>
         * This method runs until either
         * <ul>
         * <li>the client closes the socket
         * <li>an error is encountered
         * <li>the <code>m_continue</code> is set to false
         * </ul>
         */
        private void process ()
        {
            boolean error = false; // error flag
            boolean get = false; // get request?
            boolean post = false; // post request?
            String m_httpType = null; // http protocol version
            String resource = ""; // resource requested
            String query = ""; // query part of request
            String response = ""; // generated response
            String clientHost = ""; // host issuing request
            String connDirective = null; // connection close instructions
            int contentLength = 0; // request content length
            String contentType; // response content type
            String header = ""; // response header
            String responseCode = HttpURLConnection.HTTP_OK + " OK";
            boolean close = false; // close connection?
            BufferedReader reader; // reader built from socket
            DataOutputStream clientStream; // writer built from socket

            // Set the socket timeout.
            // If a read blocks for longer than this, a regular socket will
            // throw an InterruptedIOException.
            // Unfortunately, an SSL Socket doesn't share this behavior. It
            // just returns null from the read.
            // Also, although the documentation says that the socket is still
            // valid after the timeout, this doesn't seem to be the case for
            // SSL Sockets. Once they timeout, they are dead.
            try
            {
                m_clientSocket.setSoTimeout (IDLE_TIMEOUT);
            }
            catch (SocketException se)
            {
                logger.fatal ("Unable to set socket timeout: " + IDLE_TIMEOUT, se);
            }

            // Create the readers and writers from the socket
            try
            {
                reader = new BufferedReader (new InputStreamReader (m_clientSocket.getInputStream ()));
                clientStream = new DataOutputStream (new BufferedOutputStream (m_clientSocket.getOutputStream ()));
            }
            catch (IOException e)
            {
                logger.fatal ("When creating streams from socket for server " + m_serviceName, e);
                try
                {
                    m_clientSocket.close ();
                }
                catch (IOException ioe)
                {
                }
                // Can't do anything in this case, just bail
                return;
            }

            // Now loop until someone tells us to stop
            while (m_continue)
            {
                // default the content type
                contentType = CONTENTTYPE;

                // read request from client
                String line;
                int reqId = getReqId ();

                try
                {
                    // Read the first line of the request. There are several
                    // possible outcomes here.
                    // If line is null, then either
                    // the client has closed down its end of the socket or
                    // it is an SSL Socket that has timed out.
                    // Either way, a null return means we should shut down
                    // and leave.
                    // If it is a regular (non-SSL) socket and times out, then
                    // it will throw an InterruptedIOException. We catch that
                    // below and leave.
                    // If we get a non-null return, then that is a request from
                    // the client. Note that we could have been interrupted
                    // while waiting for this request, since an outside
                    // interrupt
                    // does not cause an InterruptedIOException. So, even if
                    // we get a valid request, we need to check the m_continue
                    // flag. If it is set, then we should not process the
                    // request.
                    try
                    {
                        line = reader.readLine ();
                        if (line == null)
                        {
                            // Either client closed socket or SSL Socket timed
                            // out
                            break;
                        }
                        if (!m_continue)
                        {
                            // Got a request, but have already been told to
                            // stop. Don't process the request
                            break;
                        }
                        logger.trace (line);
                    }
                    catch (IOException ioe)
                    {
                        logger.trace ("IO Exception when reading first line of request - closing connection");
                        break;
                    }

                    // The first line is the http header line
                    // It consists of the method name, the requested resource
                    // and the http protocol version
                    StringTokenizer tok = new StringTokenizer (line, " ");
                    if (tok.countTokens () != 3)
                    {
                        error = true;
                        responseCode = HttpURLConnection.HTTP_BAD_REQUEST + " Bad Request - Invalid header: " + line;
                    }
                    else
                    {
                        String token = tok.nextToken (); // the http method
                        if (token.equalsIgnoreCase ("GET"))
                        {
                            get = true;
                        }
                        else if (token.equalsIgnoreCase ("POST"))
                        {
                            post = true;
                        }
                        // else
                        // An unsupported method - this will error out
                        // later if we just leave get and post both false

                        resource = tok.nextToken ();
                        m_httpType = tok.nextToken ();
                    }

                    // Read the rest of the header, it consists of
                    // name value pairs. We're only interested in a few
                    // of the names
                    boolean haveExpect = false;
                    boolean expectContinue = false;
                    try
                    {
                        while ((line = reader.readLine ()) != null)
                        {
                            if (line.length () == 0)
                            {
                                // A blank line terminates the header
                                break;
                            }

                            logger.trace (line);

                            // Get the name and value
                            String name = null;
                            String value = null;
                            int space = line.indexOf (" ");
                            if (space == -1)
                            {
                                // Just one word, its the name and there is no
                                // value
                                name = line.trim ();
                                value = "";
                            }
                            else
                            {
                                name = line.substring (0, space).trim ();
                                value = line.substring (space).trim ();
                            }
                            // Process the names we care about
                            if (name.equalsIgnoreCase (CONNECTIONHEADER))
                            {
                                connDirective = value;
                            }
                            else if (name.equalsIgnoreCase (HOST))
                            {
                                clientHost = value;
                            }
                            else if (name.equalsIgnoreCase (CONTENTLENGTH))
                            {
                                contentLength = Integer.parseInt (value);
                            }
                            else if (name.equalsIgnoreCase (EXPECT))
                            {
                                haveExpect = true;
                                expectContinue = EXPECT_CONTINUE.equalsIgnoreCase (value);
                            }
                        }
                    }
                    catch (InterruptedIOException ioe)
                    {
                        // Socket timed out on read - time to quit
                        logger.trace ("RequestHandler - socket timed out");
                        break;
                    }
                    catch (IOException i)
                    {
                        if (Thread.interrupted ())
                        {
                            // We were interrupted - time to leave
                            break;
                        }
                        error = true;
                        responseCode = HttpURLConnection.HTTP_CONFLICT + " Temporary conflict - IOException "
                                       + i.getMessage ();
                        logger.trace ("Error " + responseCode, i);
                    }

                    // do some basic validation
                    if (!error && !get && !post)
                    {
                        // This server only handles GETs and POSTs.
                        error = true;
                        // Hard code HttpURLConnection.HTTP_NOT_IMPLEMENTED
                        // (JDK1.3)
                        responseCode = 501 + " NOT IMPLEMENTED";
                        logger.trace ("Error " + responseCode);
                    }

                    // Check protocol type and set default value for close
                    if (!error)
                    {
                        if (m_httpType.equalsIgnoreCase (HTTPHEADER1_0))
                        {
                            // HTTP 1.0 defaults to close
                            close = true;
                            logger.trace ("Close because " + HTTPHEADER1_0);
                        }
                        else if (m_httpType.equalsIgnoreCase (HTTPHEADER1_1))
                        {
                            // HTTP 1.1 defaults to keep open
                            close = false;
                        }
                        else
                        {
                            // An unknown protocol version
                            error = true;
                            responseCode = HttpURLConnection.HTTP_VERSION + " HTTP VERSION";
                            logger.trace ("Error " + responseCode);
                        }
                    }

                    // If the client included an Expect: header, process it
                    if (haveExpect)
                    {
                        // If client expects a continue, send it.
                        // If client expects something else, we can't deal with
                        // it
                        String expectResponseCode;
                        if (expectContinue)
                        {
                            expectResponseCode = "100 Continue";
                        }
                        else
                        {
                            expectResponseCode = "417 Expectation failed";
                            error = true;
                        }
                        String expectHdr = m_httpType + ' ' + expectResponseCode + "\r\n\r\n";
                        try
                        {
                            clientStream.writeBytes (expectHdr);
                            clientStream.flush ();
                        }
                        catch (IOException e)
                        {
                            logger.fatal ("Service request error cannot send to client " + e.getMessage (), e);
                            close = true;
                        }
                    }

                    // override if connection directive was included (close or
                    // keep-alive)
                    if (connDirective != null)
                    {
                        if (connDirective.equalsIgnoreCase (CLOSECONNECTION))
                        {
                            close = true;
                            logger.trace ("Close because " + CLOSECONNECTION);
                        }
                        else if (connDirective.equalsIgnoreCase (KEEPALIVE))
                        {
                            close = false;
                        }
                    }

                    if (error)
                    {
                        // There was an error
                        response = "exception=" + HttpUtil.URLEncode (responseCode);
                    }
                    else
                    {
                        // if post get query parameters
                        if (post && contentLength > 0)
                        {
                            char[] body = new char[contentLength];
                            try
                            {
                                int bytesRead = 0;
                                while (bytesRead < contentLength)
                                {
                                    bytesRead += reader.read (body, bytesRead, contentLength - bytesRead);
                                }
                            }
                            catch (IOException ioe)
                            {
                                continue;
                            }
                            // This assumes that the body of the post is a
                            // query as would be the case if it came from an
                            // html form. But that isn't necessarily the case.
                            // May need to rethink this later.
                            query = HttpUtil.getQuery ("?" + new String (body));
                            logger.trace ("POST Request: " + query);
                        }
                        else
                        {
                            // get the parameters from the url reference
                            query = HttpUtil.getQuery (resource);
                            logger.trace ("GET Request: " + query);
                        }

                        // decode parameters into a HashMap
                        HashMap parameters = null;
                        try
                        {
                            if (logger.isTraceEnabled ())
                            {
                                logger.trace ("Received request: " + HttpUtil.getPath (resource) + query);
                            }

                            // Can throw an exception if query contains control
                            // characters
                            parameters = HttpUtil.decodeParameters (query);
                        }
                        catch (Exception e)
                        {
                            logger.warn ("Exception occurred decoding parameters in query string (" + query + ")", e);
                            error = true;
                            responseCode = 400 + " BAD REQUEST";
                            response = "exception=" + HttpUtil.URLEncode (responseCode);
                        }

                        if (!error)
                        {
                            // save entire query as a parameter
                            parameters.put ("_QUERY_ARGS_", query);

                            // save client host as a parameter
                            parameters.put ("_CLIENT_HOST_", clientHost);

                            // process the request
                            response = m_rp.processRequest (HttpUtil.getPath (resource), parameters, m_clntInfo);

                            // check for a response code
                            String rcode = (String) parameters.get ("_RESPONSE_CODE_");
                            if (rcode != null)
                            {
                                // request processor has a response that is not
                                // ok
                                responseCode = rcode;
                            }

                            // check for test case instrumentation
                            String testcase = (String) parameters.get ("_TC_");
                            if (testcase != null)
                            {
                                // return responseCode as part of the response
                                // body
                                response += "&responseCode=" + HttpUtil.URLEncode (responseCode);
                                // make the response code ok
                                responseCode = HttpURLConnection.HTTP_OK + " OK";
                                // include the testcase
                                response += "&_TC_=" + HttpUtil.URLEncode (testcase);
                            }

                            // check for javascript content type
                            String type = (String) parameters.get ("_CONTENT_TYPE_");
                            if ("TEXT/JAVASCRIPT".equalsIgnoreCase (type))
                            {
                                // change content type
                                contentType = type;

                                // modify response to be javascript
                                String resp = "var ";
                                resp += StringUtil.replace (response, "=", "=\'");
                                resp = StringUtil.replace (resp, "&", "\',");
                                response = resp + "\';";
                            }
                            else
                            {
                                contentType = type;
                            }
                        }
                    }

                    try
                    {
                        if (logger.isTraceEnabled ())
                        {
                            logger.trace ("RequestHandler.responseCode:" + responseCode + " RequestHandler.response:"
                                          + HttpUtil.URLDecode (response));
                        }

                    }
                    catch (Exception e)
                    {
                        logger.trace ("RequestHandler.responseCode:" + responseCode
                                      + " RequestHandler.response (not decoded):" + response);

                    }

                    // construct the response header
                    header = constructHeader (m_httpType, responseCode, response, contentType);
                    logger.trace ("RequestHandler.constHdr:\n" + header);

                    // send the response back
                    logger.trace ("Response to request " + ": " + response);
                    try
                    {
                        clientStream.writeBytes (header);
                        if (response != null)
                        {
                            logger.trace ("RequestHandler WritingBytes:" + response);
                            clientStream.writeBytes (response);
                        }
                        clientStream.flush ();
                    }
                    catch (Exception e)
                    {
                        logger.fatal ("Service request error cannot send to client " + e.getMessage (), e);
                        close = true;
                    }

                    if (close || error)
                    {
                        logger.trace ("Closing connection (close=" + close + " error=" + error + ")");
                        break;
                    }
                }
                finally
                {
                }

            }

            // Finished - clean up and exit
            try
            {
                reader.close ();
                clientStream.close ();
                m_clientSocket.close ();
            }
            catch (IOException e)
            {
            }
        }

        /** Clears the m_continue flag so we'll stop */
        void stop ()
        {
            m_continue = false;
        }

        /**
         * Build an HTTP response header.
         * 
         * @param httpType the http version (http/1.0 or http/1.1
         * @param responseCode the response code (e.g. '200 OK')
         * @param response body of the response
         * @param contentType content type
         * @return the complete http response header
         */
        private String constructHeader (String httpType, String responseCode, String response, String contentType)
        {
            int responseLen = 0;
            if (response != null)
            {
                responseLen = response.length ();
            }

            String header = httpType + " " + responseCode + "\r\n" + "Allow: GET, POST\r\n" + "Server: "
                            + m_serviceName + "\r\n" + "Date: " + HttpUtil.httpDate (new Date ()) + "\r\n"
                            + "Content-Type: " + contentType + "\r\n" + "Content-Length: " + responseLen + "\r\n"
                            + "\r\n";
            return (header);
        }
    }

    /** A thread to service requests. */
    private static class ServiceThread extends Thread
    {
        private volatile boolean m_continue = true;

        public void run ()
        {
            Request req = null; // request we are processing

            // Go until we are told to stop
            while (m_continue)
            {
                // Look for a request on the queue
                synchronized (s_requestQ)
                {
                    while (s_requestQ.size () == 0)
                    {
                        try
                        {
                            s_requestQ.wait ();
                        }
                        catch (InterruptedException e)
                        {
                            if (!m_continue)
                            {
                                // We are going away
                                s_idleThreadCount--;
                                return;
                            }
                        }
                    }

                    // Get the first request from the queue
                    req = (Request) s_requestQ.remove (0);

                    // We're working - one less idle thread
                    s_idleThreadCount--;
                }

                // Process the request
                req.process ();

                if (!m_continue)
                {
                    // Everything is being shutdown
                    return;
                }
                // Remove the request from the set of all requests and
                // put us back in the idle thread pool if the pool isn't
                // already full
                synchronized (s_requestQ)
                {
                    s_allRequests.remove (req);
                    // Drop the reference to the request so it can be garbage
                    // collected
                    req = null;
                    if (s_idleThreadCount >= IDLE_POOL_SIZE)
                    {
                        // Idle pool is full, so this thread won't
                        // be re-used
                        s_allThreads.remove (this);
                        return;
                    }
                    // Still some room in the idle pool, so
                    // this thread can be re-used
                    s_idleThreadCount++;
                }
            }
        }
    }

    // Generate unique request id's for logging
    private static int reqId = 0;

    private static synchronized int getReqId ()
    {
        return ++reqId;
    }

}